//
//  baseshader.cpp
//  xed_vfx_engine
//
//  Created by 岳伸 富田 on 11/10/31.
//  Copyright (c) 2011年 __MyCompanyName__. All rights reserved.
//

#include "baseshader.h"
#include <stdio.h>
#include <string.h>

static bool checkShader(GLuint ShaderName, const char* message)
{
	if(!ShaderName)
		return false;
	
	GLint Result = GL_FALSE;
	glGetShaderiv(ShaderName, GL_COMPILE_STATUS, &Result);
    
	fprintf(stdout, "Compiling %s\n",message);
    
	int InfoLogLength;
	glGetShaderiv(ShaderName, GL_INFO_LOG_LENGTH, &InfoLogLength);

	if(InfoLogLength>4096){
		char* strbuf=new char [InfoLogLength];
		glGetShaderInfoLog(ShaderName, InfoLogLength, NULL, strbuf);
		fprintf(stdout, "%s\n", strbuf);
        delete [] strbuf;
	} else {
		char strbuf[4096];
		glGetShaderInfoLog(ShaderName, InfoLogLength, NULL, strbuf);
		fprintf(stdout, "%s\n", strbuf);
	}
	
	return Result == GL_TRUE;
}

static bool checkProgram(GLuint ProgramName)
{
	if(!ProgramName)
		return false;

	GLint Result = GL_FALSE;
	glGetProgramiv(ProgramName, GL_LINK_STATUS, &Result);
	
	fprintf(stdout, "Linking program\n");

	int InfoLogLength;
	glGetProgramiv(ProgramName, GL_INFO_LOG_LENGTH, &InfoLogLength);

	if(InfoLogLength>4096){
		char* strbuf=new char [InfoLogLength];
		glGetProgramInfoLog(ProgramName, InfoLogLength, NULL, strbuf);
		fprintf(stdout, "%s\n", strbuf);
        delete [] strbuf;
	} else {
		char strbuf[4096];
		glGetProgramInfoLog(ProgramName, InfoLogLength, NULL, strbuf);
		fprintf(stdout, "%s\n", strbuf);
	}
	
	return Result == GL_TRUE;
}

/**
 * Name: createProgram
 * Desc: GLSLシェーダーソースコードからシェーダーProgram作成
 */
static GLuint createProgram(const char* vertexShaderSource,
					 const char* fragmentShaderSource,
					 const char** messages=NULL)
{
	bool Validated = true;
	GLuint ProgramName = 0;
	
	// Compile a shader
	GLuint VertexShaderName = 0;
	if(Validated)
	{
		char const * Source = vertexShaderSource;
		VertexShaderName = glCreateShader(GL_VERTEX_SHADER);
		glShaderSource(VertexShaderName, 1, &Source, NULL);
		glCompileShader(VertexShaderName);
		
#ifdef DEBUG
		if(messages==NULL){
			Validated = checkShader(VertexShaderName,"VertexShader");
		} else {
			Validated = checkShader(VertexShaderName,messages[0]);
		}
#endif
	}
    
	// Compile a shader
	GLuint FragmentShaderName = 0;
	if(Validated)
	{
		char const * Source = fragmentShaderSource;
		FragmentShaderName = glCreateShader(GL_FRAGMENT_SHADER);
		glShaderSource(FragmentShaderName, 1, &Source, NULL);
		glCompileShader(FragmentShaderName);
		
#ifdef DEBUG
		if(messages==NULL){
			Validated = checkShader(FragmentShaderName,"FragmentShader");
		} else {
			Validated = checkShader(FragmentShaderName,messages[1]);
		}
#endif
	}
	
	// Link a program
	if(Validated)
	{
		ProgramName = glCreateProgram();
		glAttachShader(ProgramName, VertexShaderName);
		glAttachShader(ProgramName, FragmentShaderName);
		glDeleteShader(VertexShaderName);
		glDeleteShader(FragmentShaderName);
	}
	
	return ProgramName;
}

namespace Graphic
{
/**
 * Class: baseshader
 * Desc:
 */

/**
 * Name: ActivateUniformLocation
 * Desc:
 */
  bool BaseShader::ActivateUniformLocation(const ShaderID shaderId, const char* locName, unsigned int locIndex)
{
  if(shaderId < 0){
	return false;
  }

  if(shaderId >= (int)m_shaderSets.size()){
	return false;
  }
  
  if(locIndex>=MAX_UNIFORMLOCATION_SET){
	return false;
  }
  
  GLint* pUniformLocations=m_shaderSets[shaderId].uniformLocationSet.uniformLocations;
  if(pUniformLocations[locIndex]!=-1){
	return false;
  }
  
  GLint uniformLocValue=glGetUniformLocation(m_shaderSets[shaderId].shader,locName);
  if(uniformLocValue==-1){
	return false;
  }
  
  pUniformLocations[locIndex]=uniformLocValue;
  m_shaderSets[shaderId].uniformLocationSet.uniformLocationCount+=1;
  return true;
} 

/**
 * Name: Bind
 * Desc:
 */
bool BaseShader::Bind(const ShaderID shaderId)
{
  if(shaderId < 0){
	return false;
  }
  if(shaderId >= (int)m_shaderSets.size()){
	return false;
  }
  
  glUseProgram(m_shaderSets[shaderId].shader);
  m_currentShaderId=shaderId;
  
  return true;
}

/**
 * Name: Unbind
 * Desc:
 */
void BaseShader::Unbind()
{
  glUseProgram(0);
}

/**
 * Name: SetWVP
 * Desc:
 */
void BaseShader::SetWVP(const VMA::Matrix4& wvp)
{  
  glUniformMatrix4fv(m_shaderSets[m_currentShaderId].specificUniformLocations.uniformLocationWorldViewProj,1,false,(float*)&wvp);
}

/**
 * Name: SetWorldMatrix
 * Desc:
 */
void BaseShader::SetWorldMatrix(const VMA::Matrix4& world)
{  
  glUniformMatrix4fv(m_shaderSets[m_currentShaderId].specificUniformLocations.uniformLocationWorld,1,false,(float*)&world);
}

/**
 * Name: SetViewMatrix
 * Desc:
 */
void BaseShader::SetViewMatrix(const VMA::Matrix4& view)
{  
  glUniformMatrix4fv(m_shaderSets[m_currentShaderId].specificUniformLocations.uniformLocationView,1,false,(float*)&view);
}

/**
 * Name: SetProjMatrix
 * Desc:
 */
void BaseShader::SetProjMatrix(const VMA::Matrix4& proj)
{  
  glUniformMatrix4fv(m_shaderSets[m_currentShaderId].specificUniformLocations.uniformLocationProj,1,false,(float*)&proj);
}

/**
 * Name: SetUniform4fv
 * Desc:
 */
void BaseShader::SetUniform4fv(unsigned int locIndex, const float* value, unsigned int arrayCount)
{
  glUniform4fv(m_shaderSets[m_currentShaderId].uniformLocationSet.uniformLocations[locIndex],arrayCount >> 2,value);
}

/**
 * Name: SetUniform4f
 * Desc:
 */
void BaseShader::SetUniform4f(unsigned int locIndex, const VMA::Vector4& v)
{
  glUniform4fv(m_shaderSets[m_currentShaderId].uniformLocationSet.uniformLocations[locIndex],1,(float*)&v);
}

/**
 * Name: SetUniformMatrix4fv
 * Desc:
 */
void BaseShader::SetUniformMatrix4fv(unsigned int locIndex, const float* value, unsigned int arrayCount)
{
  glUniformMatrix4fv(m_shaderSets[m_currentShaderId].uniformLocationSet.uniformLocations[locIndex],arrayCount >> 2,false,value);
}

/**
 * Name: SetUniformMatrix4f
 * Desc:
 */
void BaseShader::SetUniformMatrix4f(unsigned int locIndex, const VMA::Matrix4& m)
{
  glUniformMatrix4fv(m_shaderSets[m_currentShaderId].uniformLocationSet.uniformLocations[locIndex],1,false,(float*)&m);
}


/**
 * Name: buildShader
 * Desc:
 */
ShaderID BaseShader::buildShader(const char* vertexShadrSource, const char* fragmentShaderSource)
{
  GLuint shader=createProgram(vertexShadrSource,fragmentShaderSource);
  if(shader==0){
	return -1;
  }
  
  glBindAttribLocation(shader,0,"vertex");

  glLinkProgram(shader);
  
  bool stat=checkProgram(shader);
  if(stat==false){
	return -1;
  }
  
  ShaderSet shaderSet;
  shaderSet.shader=shader;
  shaderSet.uniformLocationSet.uniformLocationCount=0;
  for(unsigned int i=0;i<MAX_UNIFORMLOCATION_SET;i++){
	shaderSet.uniformLocationSet.uniformLocations[i]=-1;
  }
  
  memset(&shaderSet.specificUniformLocations,-1,SPECIFIC_UNIFORMLOCATION_COUNT);

  /* get uniform location of world */
  shaderSet.specificUniformLocations.uniformLocationWorld=glGetUniformLocation(shader,"World");
  
  /* get uniform location of view */
  shaderSet.specificUniformLocations.uniformLocationView=glGetUniformLocation(shader,"View");
  
  /* get uniform location of proj */
  shaderSet.specificUniformLocations.uniformLocationProj=glGetUniformLocation(shader,"Proj");
  
  /* get uniform location of worldviewproj */
  shaderSet.specificUniformLocations.uniformLocationWorldViewProj=glGetUniformLocation(shader,"WorldViewProj");
  
  m_shaderSets.push_back(shaderSet);
  
  return (m_shaderSets.size()-1); 
}


} // namespace Graphic
